﻿'    DotNetIRCD - An IRC Daemon written in VB.Net
'    Copyright (C) 2012 GeorgeBush
'
'    This program is free software: you can redistribute it and/or modify
'    it under the terms of the GNU General Public License as published by
'    the Free Software Foundation, either version 2 of the License, or
'    (at your option) any later version.
'
'    This program is distributed in the hope that it will be useful,
'    but WITHOUT ANY WARRANTY; without even the implied warranty of
'    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
'    GNU General Public License for more details.
'
'    You should have received a copy of the GNU General Public License
'    along with this program. If not, see <http://www.gnu.org/licenses/>.

Imports System.Net.Sockets
Imports System.Net
Imports System.Threading
Imports System.Diagnostics.CodeAnalysis

''' <summary>
''' This class implements a server binding
''' </summary>
''' <remarks>This class creates the listening socket</remarks>
Friend NotInheritable Class ServerBinding

    ''' <summary>
    ''' Stores a copy of the BindPoint data for this Class/Thread
    ''' </summary>
    Private ThisBinding As BindPoint = Nothing

    ''' <summary>
    ''' Tells this binding to stop listening if set to true
    ''' </summary>
    Private StopListen As Boolean = False

    ''' <summary>
    ''' 
    ''' </summary>
    Private pClients As New Hashtable

    ''' <summary>
    ''' 
    ''' </summary>
    Private pClientThreads As New Hashtable

    ''' <summary>
    ''' 
    ''' </summary>
    Private pClientCount As Integer = 0

    ''' <summary>
    ''' Saves a copy of the BindPoint Info to the class
    ''' </summary>
    ''' <param name="BindInfo">The BindPoint info to save</param>
    Public Sub New(ByVal BindInfo As BindPoint)
        ThisBinding = BindInfo
    End Sub

    ''' <summary>
    ''' Logs a Message to the appropriate LogTarget(s)
    ''' </summary>
    ''' <param name="MessageType">The Type of Message to Log</param>
    ''' <param name="Message">The Message to Log</param>
    Friend Sub Log(ByVal MessageType As LogTypeEnum, ByVal Message As String)
        ServerSettings.Logger.Log(MessageType, Message)
    End Sub

    ''' <summary>
    ''' This Server Bindings Client Thread dictionary (HashTable)
    ''' </summary>
    Friend ReadOnly Property ClientThreads As Hashtable
        Get
            Return pClientThreads
        End Get
    End Property

    ''' <summary>
    ''' This thread is what a server actually runs in
    ''' </summary>
    Public Sub StartThread()

        Try

            ' Show the log message
            Log(LogTypeEnum.Info, String.Format("Thread starting for {0}:{1} SSL[{2}]", ThisBinding.IPHostName, ThisBinding.Port, ThisBinding.SSL))

            ' Start to listen
            StartListen()

            ' This is shown at the natural end of the thread
            Log(LogTypeEnum.Info, String.Format("Thread Exited {0}:{1} SSL[{2}]", ThisBinding.IPHostName, ThisBinding.Port, ThisBinding.SSL))

            ' Catch a ThrThreadAbortException, thrown when the thread is told to abort
        Catch AbortException As Threading.ThreadAbortException

        Catch ex As Exception

            ' Display the Error Message
            Log(LogTypeEnum.Error, ex.Message)

        End Try

    End Sub

    ''' <summary>
    ''' This Sub is started in a new Thread for each Server Binding
    ''' </summary>
    Private Sub StartListen()

        Try

            ' Create the Listener Socket
            Dim tListener As TcpListener = Nothing
            Dim tIPAddress As IPAddress = Nothing

            Try

                ' Try and get the Address to bind to
                ' Don't use GetHostEntry as it likes to return IPv6...
                tIPAddress = Dns.Resolve(ThisBinding.IPHostName).AddressList(0)

            Catch ex As Exception

                Log(LogTypeEnum.Error, "Failed to resolve Hostname/IP " & ThisBinding.IPHostName)
                Log(LogTypeEnum.Error, ex.Message)

            End Try

            Try

                ' start listening
                tListener = New TcpListener(New IPEndPoint(tIPAddress, ThisBinding.Port))
                tListener.Start(100)
                Log(LogTypeEnum.Info, "Now listening on " & tIPAddress.ToString & ":" & ThisBinding.Port)

            Catch ex As Exception

                Log(LogTypeEnum.Error, "Failed to listen on " & ThisBinding.IPHostName & ":" & ThisBinding.Port)
                Log(LogTypeEnum.Error, ex.Message)

            End Try

            ' This loop is to accept connections
            Do

                ' Check for pending connections
                If tListener.Pending Then

                    ' Log that a connection is pending
                    Log(LogTypeEnum.Debug, "Connection Pending.")

                    ' Create a new instance of the ClientStartData Class
                    Dim tStartData As New ClientConnection.ClientStartData

                    ' Accept the connection
                    tStartData.MySocket = tListener.AcceptTcpClient()

                    ' Tell the ClientConnection class its Parent BindPoint
                    tStartData.Parent = ThisBinding

                    ' Tell the ClientConnection its Client Number
                    pClientCount += 1
                    tStartData.ClientNumber = pClientCount

                    ' Create a new ClientConnection Class for this Client
                    Dim tClient As New ClientConnection(tStartData)
                    pClients.Add(pClientCount.ToString, tClient)

                    ' Create the new Thread for the Client Handler
                    Dim tThread As New Thread(AddressOf tClient.HandleClient)
                    pClientThreads.Add(pClientCount.ToString, tThread)

                    ' Start the Thread
                    tThread.Start()

                End If

                ' If this binding has been told to close the close
                If StopListen Then Exit Do

            Loop

            ' Do tear down
            TearDown()

            Try

                ' Stop listening and clear the TCPListener object
                tListener.Stop()
                tListener = Nothing

            Catch ex As Exception

                Log(LogTypeEnum.Error, "Failed to stop listener on " & ThisBinding.IPHostName & ":" & ThisBinding.Port)
                Log(LogTypeEnum.Error, ex.Message)

            End Try

        Catch tEX As ThreadAbortException

            ' Trigger the tear down
            TearDown()
            Log(LogTypeEnum.Debug, String.Format("Thread Aborted {0}:{1} SSL[{2}]", ThisBinding.IPHostName, ThisBinding.Port, ThisBinding.SSL))

        Catch ex As Exception

            Log(LogTypeEnum.Error, "Unhandled Exception: " & ex.Message)

        End Try

    End Sub

    ''' <summary>
    ''' This sub does all tear down for a Server Binding
    ''' </summary>
    Private Sub TearDown()

        Do

            ' Cycle through the client threads and close them
            For Each tEntry As DictionaryEntry In pClientThreads

                ' Get the thread object from the HashTable
                Dim ThisClientThread = CType(tEntry.Value, Thread)

                ' This client is alive so abort it
                If ThisClientThread.IsAlive Then
                    ThisClientThread.Abort()
                Else

                    ' Client is dead so remove it's objects
                    pClientThreads.Remove(CStr(tEntry.Key))
                    pClients.Remove(CStr(tEntry.Key))
                    Exit For
                End If

            Next

            ' All clients have aborted so we can exit this loop
            If pClientThreads.Count = 0 Then
                Exit Do
            End If

        Loop

    End Sub

End Class